import logging
import json
import base64
import click
import urllib
import importlib
import traceback
import os
import datetime
from werkzeug.exceptions import HTTPException
from utils.exception import HttpErrException, Http200ErrException
from flask import Flask, Blueprint, request
from flask_mongoengine import MongoEngine
from flask_security import Security
from flask_security.datastore import MongoEngineDatastore, MongoEngineUserDatastore
from flask_restplus import Api, Resource
from flask_cors import CORS
from exlib.flex import Flex
from exlib.webbase.http_utils import get_real_ip
from exlib.mongoengine.document import DocumentMixin
from app.database import mongo_init, redis_client
from app.logging import logger, logger_web_access
app_base = None
api_base = Api(version='1.0', title='invest sys API', description='invest sys API doc', doc='/doc/')
db = MongoEngine()
env = None
ACTIONS = (
    'web_backend',  # web后端
    'cmd',  # shell命令
    'task'  # 定时任务
)


def create_app(run_mode='web_backend__task'):
    global app_base, api_base, db, env
    from app.conf import Config, ENV, RUN_CONFIG
    env = ENV
    app = Flask(__name__)
    Flex().init_app(
        app=app,
        db_engine='mongo',
        apis={
            'api': api_base
        },
        commonly_documents={
            'user': 'Account',
            'role': 'Role',
            'permission': 'Permission',
            'domain': 'Company'
        },
        domain_key='company_id',
        default_domain='skyviewfund',
        force_domain=RUN_CONFIG.RUNNING_ENTERPRISE,
        dbs={"mongo": {'default': 'default',
                       'org_db': 'org_db',
                       'base_skv': 'base_skv',
                       'portfolio': 'portfolio'
                       }}
    )
    from utils.response import SkvSucResponse, SkvTable, ListResponse, error_fields
    app.flex.update(
        formats={
            'Response': SkvSucResponse,
            'Table': SkvTable,
            'ListResponse': ListResponse,
            'error_fields': error_fields
        }
    )
    app_base = Flask(__name__)
    app_base.config.from_object(RUN_CONFIG)
    app_base.access_log = logger_web_access
    db.init_app(app_base)
    mongo_init(app_base)
    redis_client.init_app(app_base)
    run_mode = run_mode or app_base.config["RUN_MODE"]
    init_cmd(app_base)
    if 'web_backend' in run_mode:
        init_api(app_base)
        reset_permissions()
    if 'task' in run_mode:
        init_task(app_base)
    init_app_post(app)
    return app_base


def init_api(app):
    # 支持跨域
    global api_base
    from app.user.models import User, Role
    cors = CORS(app, supports_credentials=True, resources={r"/*": {"origins": "*"}})
    user_datastore = MongoEngineUserDatastore(db, User, Role)
    security = Security(app, user_datastore)
    security.login_manager.login_view = None
    blueprint = Blueprint('api', __name__, url_prefix='/apis')
    api_base.init_app(blueprint)
    app.register_blueprint(blueprint)
    init_listed_views()

    @api_base.errorhandler(HTTPException)
    def handle_basic_exception(error):
        if request:
            description = request.url + '\n' + error.description
        else:
            description = error.description
        logger.log(logging.INFO, description)
        return {'message': error.description}, error.code

    @api_base.errorhandler(HttpErrException)
    def handle_http_exception(error):
        """
            已知异常进入到 wow_backend_web，默认级别为告警，可以替换为error 或 info
        :param error:
        :return:
        """
        from flask_login import current_user
        payload = None
        if request:
            description = request.url + '\n' + error.description
            if request.method == "GET":
                payload = request.args
            else:
                try:
                    payload = request.get_json()
                    if request.args:
                        payload.update(request.args)
                except:
                    payload = request.args
        else:
            description = error.description

        logging_dict = dict(
                request=request.method, path=request.path, ip=get_real_ip(),
                time=datetime.datetime.now(),
                agent_platform=request.user_agent.platform,
                agent_browser=request.user_agent.browser,
                agent_browser_version=request.user_agent.version,
                agent=request.user_agent.string,
                user_db_id=str(current_user.id) if (current_user and not current_user.is_anonymous) else "",
                user_name=current_user.name.encode("utf-8") if (current_user and not current_user.is_anonymous) else "",
                payload=json.dumps(payload, ensure_ascii=False) if payload else None,
                description=description
            )
        logger.log(error.level, logging_dict)
        return {'message': error.description, 'error': error.err}, error.code

    @security.login_manager.unauthorized_handler
    def unauthorized():
        from exlib.restviewset.response import JsonResponse
        return JsonResponse(data={'code': 1, 'message': '用户未登录'}, status=401)

    @security.unauthorized_handler
    def permission_deny():
        from exlib.restviewset.response import JsonResponse
        return JsonResponse(data={'code': 1, 'message': '您没有该项操作的权限'}, status=403)


def init_listed_views():
    """列出各模块的views"""
    from app.user.views import init
    init(api_base)


def init_cmd(app):
    from app.logging import logger_script

    @app.cli.command("cmd", help="custom command (located in scripts dir)")
    @click.option('-i', '--file', help='file path (must in scripts dir)')
    @click.option('-f', '--func', help='the function you need run')
    @click.argument('args', nargs=-1)
    def cmd(file, func, args):
        try:
            mod = importlib.import_module('scripts.%s' % file)
            method = getattr(mod, func)
        except Exception as e:
            logger_script.error("load module function error: %s - %s\n %s" % (file, func, str(e)))
            return
        try:
            logger_script.info("begin function: %s - %s - %s" % (file, func, str(args)))
            method(*args)
            logger_script.info("finish function: %s - %s" % (file, func))
        except:
            logger_script.error("execute function error: %s - %s - %s" % (file, func, str(args)))
            logger_script.error(traceback.format_exc())

    return app


def init_task(app_base):
    pass


def reset_permissions():
    from app.perms import sync_permissions
    sync_permissions()


def init_app_post(app):
    """特意放在app初始化最后"""
    from exlib.schema import register_all_schemas
    register_all_schemas()
